package org.fujene.communicate.local;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.fujene.index.IndexingSystem;
import org.fujene.memman.LocalMemoryManager;
import org.fujene.search.SearchSystem;
import org.fujene.structure.Enumerates;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.Result;

/**
 * Peer to Peerサービスです。 ここが全サービスの本体となります。
 */
public class LocalPeerSystem{
	// MemoryManager -> Memory, Disk
	private LocalMemoryManager memman = null;

	public boolean stopFlag = false;

	private ExecutorService execIndex = null;
	private ExecutorService execSearch = null;

	private String directory = null;
	private int blockNum = 0;
	private int blockSize = 0;

	private boolean debugFlag = false;

	// //////// P2P ///////////
	private static final int INDEX_NOREP = 2;

	// //////// Logger ///////////
	private Logger mainLogger = null;

	private boolean getArgs(String[] args){
		boolean isEnded = false;
		String settingFile;

		if (args.length > 2 || args.length == 1){
			System.err.println("Usage: PeerSystem [--setting <SettingFile>] [--secondary <IPAddress ...>]");
			return isEnded;
		}
		// デフォルト値
		this.blockNum = 256;
		this.blockSize = 1048576;
		this.directory = "./fujene_data/";
		this.mainLogger = Logger.getLogger("Fujene search engine");

		if (args.length > 1 && args[0].equals("--setting")){
			settingFile = args[1];

			BufferedReader br = null;
			String input = null;
			try{
				br = new BufferedReader(new FileReader(new File(settingFile)));

				while((input = br.readLine()) != null){
					// "#"始まりはコメント行
					if (!input.startsWith("#")){
						String[] settingArgs = input.split("=");
						if (settingArgs.length >= 2){
							if (settingArgs[0].equalsIgnoreCase("BlockNum"))
								blockNum = Integer.parseInt(settingArgs[1]);
							else if (settingArgs[0].equalsIgnoreCase("BlockSize"))
								blockSize = Integer.parseInt(settingArgs[1]);
							else if (settingArgs[0].equalsIgnoreCase("Directory"))
								directory = settingArgs[1];
							else if (settingArgs[0].equalsIgnoreCase("DebugMode")){
								if (settingArgs[1].equalsIgnoreCase("on")) debugFlag = true;
							}else if (settingArgs[0].equalsIgnoreCase("LogLevel")){
								try{
									mainLogger.setLevel(Level.parse(settingArgs[1]));
								}
								catch(Exception e){
									e.printStackTrace();
									mainLogger.severe("Specified log level does not exist.");
								}
							}
						}
					}

					isEnded = true;
				}
			}
			catch(FileNotFoundException e){
				System.err.println("Please input appropriate file. Search system will shutdown.");
				return isEnded;
			}
			catch(IOException e){}
			finally{
				if (br != null){
					try{
						br.close();
					}
					catch(IOException e){}
				}
			}
		}else if (args.length == 0){
			isEnded = true;
		}

		return isEnded;
	}

	public void initialize() throws IOException, InterruptedException{
		new SearchSystem(this, mainLogger);

		new IndexingSystem(this, mainLogger);

		memman = new LocalMemoryManager(blockSize, blockNum, directory, false);

		execIndex = Executors.newFixedThreadPool(1);

		execSearch = Executors.newFixedThreadPool(1);

		memman.debugFlag = debugFlag;
	}

	// ローカルで動かしたいときはこっち
	public static void main(String[] args) throws InterruptedException, IOException{
		LocalPeerSystem mainSystem = new LocalPeerSystem();

		if (!mainSystem.getArgs(args)) return;

		mainSystem.initialize();

		mainSystem.console();
	}

	private void console() throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String arg;

		try{
			Thread.sleep(400);
		}
		catch(InterruptedException ignored){}

		while(true){
			System.out.println("Queue for index: " + ((ThreadPoolExecutor)execIndex).getActiveCount()
					+ " Queue for search: " + ((ThreadPoolExecutor)execSearch).getActiveCount());

			System.out.print("Fujene > ");
			arg = br.readLine();

			String[] args = arg.split(" ");
			if (args.length > 0){
				if (args[0].equals("index") || args[0].equals("i")){
					if (args.length == 1)
						System.out.println("Specify string to index.");
					else{
						setNewIndex(new IndexQueue(Enumerates.INDEX, args[1]));
					}
				}else if (args[0].equals("search") || args[0].equals("s")){
					if (args.length == 1)
						System.out.println("Specify query.");
					else{
						try{
							Result result;
							result = setNewSearch(new IndexQueue(Enumerates.SEARCH, args[1], 0, 100, 1L));
							// ちょっとなら結果を出す
							if (result.results.length <= 10){
								for(int i = 0; i < result.results.length; i++){
									if (result.results[i].length() > 0)
										System.out.println(i + ":\t" + result.results[i]);
								}
							}
						}
						catch(Exception ignored){}
					}
				}else if (args[0].equals("searchspec") || args[0].equals("p")){
					if (args.length != 4)
						System.out.println("Specify arguments.");
					else{
						try{
							Result result = setNewSearch(new IndexQueue(Enumerates.SEARCH, args[1],
									Integer.parseInt(args[2]), Integer.parseInt(args[3]), 1L));
							System.out.println("Result total: "
									+ (result.results != null ? result.results.length : 0));
							if (result.results.length <= 10){
								for(int i = 0; i < result.results.length; i++){
									if (result.results[i].length() > 0)
										System.out.println(i + ":\t" + result.results[i]);
								}
							}
						}
						catch(Exception ignored){
							ignored.printStackTrace();
						}
					}
				}else if (args[0].equals("indexcount") || args[0].equals("c")){
					System.out.println("Total count: " + memman.getCounter());
				}else if (args[0].equals("querycount") || args[0].equals("u")){
					System.out.println("Total count: " + memman.getSearchcounter());
				}else if (args[0].equals("termcount") || args[0].equals("t")){
					System.out.println("Term count: " + memman.getTerms());
				}else if (args[0].equals("skipcount") || args[0].equals("k")){
					System.out.println("Skip ptr. count: " + memman.getSkipPointerLen());
				}else if (args[0].equals("memory") || args[0].equals("m")){
					memman.getMemoryList();
				}else if (args[0].equals("save") || args[0].equals("v")){
					saveMemory();
				}else if (args[0].equals("read") || args[0].equals("r")){
					readMemory();
				}else if (args[0].equals("quit") || args[0].equals("q")){
					setStopper(true);
				}else{
					System.out
							.println("Command: <i>ndex [args], <s>earch [args], searchs<p>ec [args] [from] [length], <t>ermcount, index<c>ount, q<u>erycount, s<k>ipcount, <m>emory, <r>ead, sa<v>e, <q>uit");
				}
			}
		}
	}

	// 外部からJUnitとかで呼び出すときはこっち
	public void testMain(String[] args) throws InterruptedException, IOException{
		this.blockNum = 32;
		this.blockSize = 1048576;
		this.directory = "./testDir/";
		this.initialize();

		memman.debugFlag = true;
	}

	// 全部の動作を止める操作
	public void setStopper(boolean isStop){
		stopFlag = isStop;
		execIndex.shutdownNow();
		execSearch.shutdownNow();
		// 無理やりな気がする
		Runtime.getRuntime().exit(0);
	}

	// 大元のindexing窓口 いずれ@deprecated
	public void setNewIndex(IndexQueue newIndex){
		newIndex.indexType = INDEX_NOREP;
		execIndex.execute(new PeerIndexContentsHost(newIndex));
	}

	public class PeerIndexContentsHost implements Runnable{
		private IndexQueue newIndex;

		public PeerIndexContentsHost(IndexQueue index){
			this.newIndex = index;
		}

		@Override
		public void run(){
			try{
				memman.index(newIndex);
			}
			catch(UnsupportedEncodingException e){}
		}
	}

	// 大元のsearch窓口
	public Result setNewSearch(IndexQueue newQuery) throws Exception{
		return memman.search(newQuery);
	}

	public void readMemory(){
		memman.initializeMemory();
	}

	public void saveMemory(){
		memman.saveMemory();
	}
}
